﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using uam_fps_game.Gfx;
using Microsoft.Xna.Framework;
using uam_fps_game.NetComponents;
using Microsoft.Xna.Framework.Input;
using uam_fps_game.Network;

namespace uam_fps_game.Scenes
{
    class PlayerNode : SceneNode, NetworkListener
    {
        /// true when node belongs to the scene on server, false when on client
        protected bool _isServerSide;

        /// true when node represents player that is controlled by the person using computer on which game instance is running.
        /// Relevant only when _isServerSide == false
        protected bool _isLocal;

        protected ModelInstance _model;

        protected float _moveSpeed;
        protected Vector3 _moveDir;

        /// Network component used when _isServerSide == true. When _isServerSide == false, _serverNetComp is expected to be null;
        protected ServerPlayerNetComp _serverNetComp = null;

        /// Network component used when _isServerSide == false. When _isServerSide == true, _clientNetComp is expected to be null;
        protected ClientPlayerNetComp _clientNetComp = null;


        internal PlayerNode(Scene scene, ModelInstance model, bool isServerSide, bool isLocal, NetComp netComp)
            : base(scene)
        {
            _model = model;
            _isServerSide = isServerSide;
            _isLocal = isLocal;

            _moveSpeed = 10.0f;

            if (isServerSide) _serverNetComp = (ServerPlayerNetComp)netComp;
            else _clientNetComp = (ClientPlayerNetComp)netComp;
        }

        protected void CreateStateMessageFromNodeState(PlayerState state)
        {
            state.Position = Position;
        }

        protected void UpdateNodeStateFromStateMessage(PlayerState state)
        {
            Position = state.Position;
        }

        /// <summary>
        /// Given player input and previous player state, simulates movement and other behaviour of player node and stores effect of simulation in the same player state parameter.
        /// </summary>
        /// <param name="input"></param>
        /// <param name="data">Is expected to </param>
        protected void ApplyInput(PlayerInput input, ref PlayerState data)
        {
            _moveDir = Vector3.Forward;

            Matrix rotation;
            Matrix.CreateFromYawPitchRoll(Rotation.Y, Rotation.X, Rotation.Z, out rotation);
            Vector3.TransformNormal(ref _moveDir, ref rotation, out _moveDir);

            Vector3 forwardMove;
            Vector3.Multiply(ref _moveDir, _moveSpeed * input.DeltaTime, out forwardMove);

            Vector3 rightMove = Vector3.Cross(forwardMove, Vector3.Up);

            Vector3 movement = Vector3.Zero;
            if (input.MovingForward)
            {
                Vector3.Add(ref movement, ref forwardMove, out movement);
            }
            if (input.MovingBackward)
            {
                Vector3.Subtract(ref movement, ref forwardMove, out movement);
            }
            if (input.MovingRight)
            {
                Vector3.Add(ref movement, ref rightMove, out movement);
            }
            if (input.MovingLeft)
            {
                Vector3.Subtract(ref movement, ref rightMove, out movement);
            }

            Vector3.Add(ref data.Position, ref movement, out data.Position);
        }

        protected void CheckInput(ref PlayerInput input)
        {
            KeyboardState keys = Keyboard.GetState();
            if (keys.IsKeyDown(Keys.Up))
            {
                input.MovingForward = true;
                input.AnyInput = true;
            }
            if (keys.IsKeyDown(Keys.Down))
            {
                input.MovingBackward = true;
                input.AnyInput = true;
            }
            if (keys.IsKeyDown(Keys.Right))
            {
                input.MovingRight = true;
                input.AnyInput = true;
            }
            if (keys.IsKeyDown(Keys.Left))
            {
                input.MovingLeft = true;
                input.AnyInput = true;
            }
        }

        protected override void Update(float deltaTime, Matrix parentWorldMatrix)
        {
            if (_isServerSide)
            {
                // for each player input received from client controlling the player, server must run node's simulation

                if (_serverNetComp.LastReceivedInputs.Count > 0)
                {
                    Queue<PlayerInput> newClientInputs = _serverNetComp.LastReceivedInputs;

                    // create PlayerState object from current server node's state
                    PlayerState newServerState = new PlayerState();
                    CreateStateMessageFromNodeState(newServerState);

                    // apply inputs one by one. Intermediate results are stored in PlayerState object
                    while(newClientInputs.Count > 0)
                    {
                        ApplyInput(newClientInputs.Dequeue(), ref newServerState);
                    }

                    // schedule state that is the effect of simulation to send to clients
                    _serverNetComp.NewStateToSend = newServerState;

                    // set node's state to simulated state
                    UpdateNodeStateFromStateMessage(newServerState);
                }
            }
            else
            {
                // for both local and nonlocal client player node's, net component is checked for new node state update that might have came from server
                if (_clientNetComp.LastReceivedState != null)
                {
                    PlayerState newServerState = _clientNetComp.LastReceivedState;

                    // setting node's state to state received from server
                    UpdateNodeStateFromStateMessage(newServerState);

                    _clientNetComp.LastReceivedState = null;
                }

                if (_isLocal)
                {
                    // creating new player input
                    PlayerInput input = new PlayerInput();
                    input.DeltaTime = deltaTime;
                    CheckInput(ref input);

                    // do not proceed if user did not do anything and input is empty
                    if (input.AnyInput)
                    {
                        // creating PlayerState object from current client node's state
                        PlayerState newClientState = new PlayerState();
                        CreateStateMessageFromNodeState(newClientState);

                        // run simulation on input and state
                        ApplyInput(input, ref newClientState);

                        // schedule state to send to server
                        _clientNetComp.NewInputsToSend.Enqueue(input);
                        
                        // set node's state to simulated state
                        UpdateNodeStateFromStateMessage(newClientState);
                    }
                }
            }




            base.Update(deltaTime, parentWorldMatrix);




            if (!_isServerSide)
            {
                _model.WorldMatrix = WorldMatrix;
            }
        }


        public void Recieve(Network.MessageTypes.MessagePrototype msg)
        {
            throw new NotImplementedException();
        }

        public List<Network.MessageTypes.Header> GetRegisteredHeaders()
        {
            throw new NotImplementedException();
        }
    }
}
